moduleを再利用して、環境ごとに異なる値を設定するTerraformアーキテクチャの一例
環境ごとに設定を変えたい
おのやんです。
みなさん、環境ごとに設定を変えて、Terraform経由でAWSリソースをデプロイしたいと思ったことはありませんか?私はあります。
例えば、開発(dev)、ステージング(stg)、本番(prd)と環境が分かれているとします。この環境それぞれに対して、ほぼ同じ構成で名前だけ変えてデプロイする流れをイメージします。その場合、moduleを共通化するディレクトリ構造を採用して、Terraformの変数を経由して環境ごとに設定を変えてデプロイすることがあります。
この構成のTerraformコードを作成する機会がありましたので、今回はそちらを紹介したいと思います。
ディレクトリ構成
具体的なディレクトリ構成がこちらになります。プロダクトのルートディレクトリにはenvironments
とmodules
の2つのディレクトリを置いています。environments
にはprod
(本番)環境のディレクトリやtest
(検証)環境のディレクトリを配置しています。またmodules
にはAmazon VPC(以下、VPC)やAmazon ECS(以下、ECS)など、管理するAWSリソースのコードを配置します。
root
├── environments
│ ├── prod
│ │ ├── main.tf # 各moduleを呼び出し、具体的な設定値を注入
│ │ └── variables.tf # 具体的な設定値を定義
│ └── test
│ ├── main.tf
│ └── variables.tf
└── modules
├── vpc
│ ├── main.tf # 各環境で共通なリソース設定
│ ├── variables.tf # environmentsから設定値を受け取るための空の変数を定義
│ └── outputs.tf # 別moduleで必要な値をoutput
└── ecs
├── main.tf
├── variables.tf
└── outputs.tf
module
の内容
各各ディレクトリは、それぞれmain.tf
、variables.tf
、outputs.tf
の3ファイルで構成されています。このうち、modules
内のvpc
配下のvariables.tf
は以下の通りです。environment
ディレクトリからの変数を受け取るための、空の変数を設定しています。
variable "system_name" {
type = string
}
variable "environment" {
type = string
}
variable "vpc_cidr" {
type = string
}
variable "private_subnet_cidr" {
type = string
}
modules
内のvpc
配下のmain.tf
は以下の通りです。呼び出し元から受け取る予定の変数を使って、リソース名を設定しています。そのためvariables
の部分には、具体的な値はまだ設定されていません。
#------------------#
# VPC
#------------------#
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.system_name}-${var.environment}-vpc"
}
}
#------------------#
# Private subnet
#------------------#
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidr
tags = {
Name = "${var.system_name}-${var.environment}-private-subnet"
}
}
modules
内のvpc
配下のoutputs.tf
は、以下のようになります。ここでは、VPCのプライベートサブネット情報がECS側のmoduleで必要になるので、output
に設定しています
output "private_subnet_id" {
description = "The ID of the private subnet"
value = aws_subnet.private.id
}
modules
内のecs
配下のvaribles.tf
では、environment
ディレクトリからの変数を受け取るための、空の変数を設定しておきます。
variable "system_name" {
type = string
}
variable "environment" {
type = string
}
variable "private_subnets" {
type = list(string)
}
ecs
配下のmain.tf
はこんな感じになります。variables.tf
の変数を、リソースの命名や設定に使用しています。
#------------------#
# ECS cluster
#------------------#
resource "aws_ecs_cluster" "ecs_cluster" {
name = "${var.system_name}-${var.environment}-ecs-cluster" # 変数を使って命名
setting {
name = "containerInsights"
value = "enabled"
}
}
#------------------#
# ECS service
#------------------#
resource "aws_ecs_service" "ecs_service_web" {
name = "${var.system_name}-${var.environment}-ecs-service-web"
cluster = aws_ecs_cluster.ecs_cluster.arn
launch_type = "FARGATE"
desired_count = 1
platform_version = "1.4.0"
network_configuration {
assign_public_ip = false
subnets = var.private_subnets # 変数を経由して設定
}
load_balancer {
container_name = "sample"
container_port = 8080
}
}
今回はECSリソース情報を使用する他のリソースがないのでoutputs.tf
は作成していません。
例えばAWS CodePipeline(以下、CodePipeline)を設定するなどの場合は、呼び出し元でECSのクラスター・サービスを指定する必要があるため、こういった時はoutputs.tf
を作成し、CodePipelineのmoduleから参照できるようにしましょう。
呼び出し元の内容
environments
内のtest
配下のvariables.tf
は以下の通りです。moduleに設定している受取用の変数に対して、ここで実際の値を定義している形です。
variable "system_name" {
default = "sample"
}
variable "environment" {
default = "test"
}
environments
内のtest
配下のmain.tf
は以下の通りです。variables.tf
で定義した実際の値を、moduleの変数経由で流し込んでいます。
module "vpc" {
source = "../../modules/vpc"
system_name = var.system_name
environment = var.environment
vpc_cidr = "10.0.0.0/16"
private_subnet_cidr = "10.0.1.0/24"
}
}
module "ecs" {
source = "../../modules/ecs"
system_name = var.system_name
environment = var.environment
private_subnets = module.vpc.private_subnet_id # vpcのoutputs.tfで設定した項目
}
このような設計にすることで、moduleをできるだけ再利用可能な状態にできます。これにより個別のリソースの名前や細かいパラメータの設定など、環境によって異なる値をvariable
で設定できるので、総コード量も少なくできるというメリットがありますね
プロダクトに合わせた構成を
今回紹介したのは、Terraformを使ったAWSリソース管理でよくやる構成ではありますが、どの粒度で変数に切り出すか、またどの粒度でmoduleを再利用するかは、各プロダクトの要件に依存します。
こちらの構成はあくまで一例ですので、これを元にTerraformの構成を考えてもらえると幸いです。では!